package cn.itsource.util;

import java.io.*;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.net.URLDecoder;
import java.sql.*;
import java.util.Date;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

//生成domain或实体类的工具类 v5
@SuppressWarnings("all")
public class EntityUtil {
	//数据库四大金刚
	private static String driverClassName = "com.mysql.jdbc.Driver";
	private static String url = "jdbc:mysql:///${database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false";
	private static String database = "inventory";
	private static String username = "root";
	private static String password = "123456";

	//生成类的包路径
	private static String packageName = "cn.itsource.domain";
	//忽略的表名前缀
	private static String ignoredTablePrefix = "t_";
	//列名下划线转小驼峰
	private static boolean columnToHump = true;
	//使用looooooooooooooooooombok
	private static boolean useLombok = false; //根据下方的开关添加对应注解
	//自动导包
	private static boolean autoImport = true;
	//生成无参构造
	private static boolean noArgsConstructor = true;
	//生成全参构造
	private static boolean allArgsConstructor = true;
	//生成无主键构造
	private static boolean noPrimaryArgsConstructor = true;
	//生成Getter和Setter方法
	private static boolean getterAndSetter = true;
	//生成toString方法
	private static boolean toString = true;
	//生成equals和hashCode方法
	private static boolean equalsAndHashCode = true;
	private static boolean useObjectsMethod = false; //使用Objects中的equals和HashCode方法 Java 7+
	//数据库time类型字段对应的Java类型
	private static Class timeOfType = Date.class;
	//数据库datetime类型字段对应的Java类型
	private static Class datetimeOfType = Date.class;
	//为Date添加格式化注解
	private static boolean addFormatAnno = true;
	private static String formatPattern = "yyy-MM-dd HH:mm:ss";


	//========================================================================
	public static void main(String[] args) {
		action();
	}
	//========================================================================


	private static Scanner scanner = new Scanner(System.in);
	private static Connection conn = null;
	private static DatabaseMetaData dbmd = null;
	private static Pattern linePattern = Pattern.compile("_(\\w)");

	/**
	 * 总控制器
	 */
	private static void action(){
		System.out.println("产品说明：此产品用于自动生成指定数据库中已存在表的实体类！");
		System.out.println("使用需知：运行之前先打开这个文件检查数据库名称密码等信息，否则连接数据库会失败！");
		System.out.println("==================================================================================");
		List<String> tables = getAllTables();
		if(tables == null) return;
		if(tables.size() == 0){
			System.err.println("当前数据库中没有表");
			return;
		}else {
			System.out.println("(a) 所有表\t(s) 手动输入表名");
		}
		for (int i = 0; i < tables.size(); i++) {
			System.out.print("("+(i+1)+") " + tables.get(i) + "\t");
			if((i+1)%5 == 0) System.out.println();
		}
		if(tables.size()%5 != 0) System.out.println();
		System.out.println("请选择要生成实体类的表名序号(多个使用空格或逗号隔开):");
		String line = scanner.nextLine();
		List<String> selectList = getSelectList(line);
		if(selectList.contains("a")){
			for(String table : tables){
				entityCreater(getAllColumn(table), getAutoIncrementColumn(table), table);
			}
			System.out.println("请刷新你的工程...");
			return;
		}
		if(selectList.contains("s")){
			System.out.println("请输入要生成实体类的表名(多个使用空格或逗号隔开):");
			String tableLine = scanner.nextLine();
			List<String> tableList = getSelectList(tableLine);
			boolean flag = false;
			for (String table : tableList) {
				table = table.trim();
				if(table.length() != 0) {
					entityCreater(getAllColumn(table), getAutoIncrementColumn(table), table);
					flag = true;
				}
			}
			if(flag){
				System.out.println("请刷新你的工程...");
			}else {
				System.out.println("未选中任何表...");
			}
			return;
		}
		ArrayList<String> tableList = new ArrayList<>();
		for (String select : selectList) {
			select = select.trim();
			try{
				int index = Integer.parseInt(select);
				tableList.add(tables.get(index-1));
			}catch (Exception e){}
		}
		for (String table : tableList) {
			entityCreater(getAllColumn(table), getAutoIncrementColumn(table), table);
		}
		if(tableList.size() != 0){
			System.out.println("请刷新你的工程...");
		}else {
			System.out.println("未选中任何表...");
		}

	}

	/**
	 * 分割输入的数据
	 * @param line
	 * @return
	 */
	private static List<String> getSelectList(String line){
		line = line.trim().toLowerCase();
		line = line.replaceAll(" ",",");
		line = line.replaceAll("，",",");
		String[] selects = line.split(",");
		return Arrays.asList(selects);
	}

	/**
	 * 获取指定表中所有的自增长列的相关信息
	 * @return
	 */
	private static List<String> getAutoIncrementColumn(String table){
		List<String> autoIncrementColumnMessage = new ArrayList<String>();
		try {
			dbmd = getConnectionByConfigFile().getMetaData();
			ResultSet colRet = dbmd.getColumns(null,"%",table,"%");
			while(colRet.next()) {
				if("YES".equals(colRet.getString("IS_AUTOINCREMENT"))){
					autoIncrementColumnMessage.add(colRet.getString("COLUMN_NAME"));
				}
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return autoIncrementColumnMessage;
	}

	/**
	 * 获取指定表中所有的列的相关信息
	 * @return
	 */
	@SuppressWarnings("all")
	private static HashMap<String,String> getAllColumn(String table){
		HashMap<String,String> columnMessage = new LinkedHashMap<String,String>();
		PreparedStatement ps;
		try {
			ps = getConnectionByConfigFile().prepareStatement("select * from `" + table + "`");
			ResultSet rs = ps.executeQuery();
			ResultSetMetaData meta =  rs.getMetaData();
			for(int i = 1;i <= meta.getColumnCount();i++){
				String columnClassName = meta.getColumnClassName(i);
				if(datetimeOfType!=null && "java.sql.Timestamp".equalsIgnoreCase(columnClassName)){
					columnClassName = datetimeOfType.toString().substring(6);
				}if(timeOfType!=null && "java.sql.Time".equalsIgnoreCase(columnClassName)){
					columnClassName = timeOfType.toString().substring(6);
				}else if("[B".equals(columnClassName)){
					columnClassName = "java.lang.String";
				}
				columnMessage.put(meta.getColumnName(i), columnClassName);
			}
		} catch (Exception e) {
			System.err.println("语句对象创建异常!");
			System.err.println(e.getMessage());
			return null;
		}
		return columnMessage;
	}

	/**
	 * 获取该数据库中所有的表组成的集合，保存的是数据库表的名称
	 * @return
	 */
	private static List<String> getAllTables(){
		List<String> tables = new ArrayList<String>();
		try {
			Connection conn = getConnectionByConfigFile();
			if(conn == null) return null;
			dbmd = conn.getMetaData();
			if(dbmd == null) return null;
			ResultSet rs = dbmd.getTables(null, "%","%",new String[]{"TABLE"});
			while(rs.next()){
				tables.add(rs.getString("TABLE_NAME"));
			}
		} catch (Exception e) {
			System.err.println("获取表元数据异常!");
			e.printStackTrace();
		}
		return tables;
	}

	/**
	 * 通过属性配置文件.properties获取连接
	 * @return
	 */
	private static Connection getConnectionByConfigFile(){
		database.length(); //此行没用, 只是为了让IDE检查到此属性被使用
		if(conn != null) return conn;
		try {
			Class.forName(driverClassName);
			conn = DriverManager.getConnection(replacePlaceholder(url), username, password);
			return conn;
		} catch (ClassNotFoundException e) {
			System.err.println("驱动加载异常：是否忘记导入驱动包？ 驱动名写错？");
			e.printStackTrace();
		} catch (SQLException e) {
			System.err.println("获取连接异常：用户名和密码写错？ mysql服务尚未打开？ url格式错误？");
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 替换占位符内容
	 * @param str
	 * @return
	 */
	private static String replacePlaceholder(String str){
		if(str == null) return str;
		int preIndex = str.indexOf("${");
		if(preIndex == -1) return str;
		int sufIndex = str.indexOf("}",preIndex+2);
		if(sufIndex == -1) return str;
		Class<?> clazz = MethodHandles.lookup().lookupClass();
		try {
			String fieldName = str.substring(preIndex + 2, sufIndex);
			Field field = clazz.getDeclaredField(fieldName);
			String fieldValue = (String)field.get(clazz);
			if(fieldValue == null) fieldValue = "";
			str = str.substring(0,preIndex) + fieldValue + str.substring(sufIndex+1);
		}catch (Exception e){
			System.err.println("替换占位符失败: " + str);
			e.printStackTrace();
		}
		return str;
	}

	/**
	 * 生成实体类
	 * @param columnMessage
	 * @param autoIncrementColumnMessage
	 * @param tableName
	 */
	private static void entityCreater(HashMap<String,String> columnMessage,List<String> autoIncrementColumnMessage,String tableName){
		if(columnMessage == null) return;
		String sourcePath = getSourcePath();
		String packageName = getPackageName();
		String writePath = sourcePath + packageName.replaceAll("\\.", "/") + "/";
		if(ignoredTablePrefix != null && ignoredTablePrefix.length() != 0 && tableName.startsWith(ignoredTablePrefix)) {
			tableName = tableName.substring(ignoredTablePrefix.length());
		}
		String fileName = lineToBigHump(tableName);
		File file = new File(writePath);
		if(!file.exists()){
			file.mkdirs();
		}
		try{
			LinkedHashSet<String> annotationSet = new LinkedHashSet<>();
			LinkedHashSet<String> importSet = new LinkedHashSet<>();
			LinkedHashMap<String, String> propertyMessage = new LinkedHashMap<>();
			for(Entry<String, String> entry:columnMessage.entrySet()){
				String columnClassName = entry.getValue();
				if(datetimeOfType!=null && "java.sql.Timestamp".equalsIgnoreCase(columnClassName)){
					columnClassName = datetimeOfType.toString().substring(6);
					importSet.add(columnClassName);
				}else if(timeOfType!=null && "java.sql.Time".equalsIgnoreCase(columnClassName)){
					columnClassName = timeOfType.toString().substring(6);
					importSet.add(columnClassName);
				}else if("[B".equals(columnClassName)){
					columnClassName = "java.lang.String";
				}
				if("java.math.BigDecimal".equalsIgnoreCase(columnClassName)
						|| "java.sql.Time".equalsIgnoreCase(columnClassName)
						|| "java.sql.Timestamp".equalsIgnoreCase(columnClassName)){
					importSet.add(columnClassName);
				}else if("java.sql.Date".equalsIgnoreCase(columnClassName)
						|| "java.util.Date".equalsIgnoreCase(columnClassName)){
					importSet.add("java.util.Date");
				}

				String propertyName = entry.getKey();
				String propertyType = entry.getValue().substring(entry.getValue().lastIndexOf(".") + 1);
				if(columnToHump){
					propertyName = lineToSmallHump(propertyName);
				}
				propertyMessage.put(propertyName, propertyType);
			}
			ArrayList<String> autoIncrementPropertyList = new ArrayList<>();
			for(String column: autoIncrementColumnMessage){
				if(columnToHump){
					autoIncrementPropertyList.add(lineToSmallHump(column));
				}else {
					autoIncrementPropertyList.add(column);
				}
			}
			int propertySize = propertyMessage.size();
			FileOutputStream fos = new FileOutputStream(writePath + fileName + ".java");
			StringBuilder word = new StringBuilder(765 * propertySize);
			StringBuilder tmpWord = new StringBuilder(128 * propertySize);;

			//初始化内容段

			//属性
			StringBuilder fieldWord = new StringBuilder(64 * propertySize);
			for(Entry<String, String> entry:propertyMessage.entrySet()){
				//是否添加格式化注解
				if(addFormatAnno && "Date".equals(entry.getValue())){
					fieldWord.append("\t@DateTimeFormat(pattern = \"").append(formatPattern).append("\")\r\n");
					fieldWord.append("\t@JsonFormat(pattern = \"").append(formatPattern).append("\",timezone=\"GMT+8\")\r\n");
					importSet.add("org.springframework.format.annotation.DateTimeFormat");
					importSet.add("com.fasterxml.jackson.annotation.JsonFormat");
				}
				fieldWord.append("\tprivate ").append(entry.getValue()).append(" ").append(entry.getKey()).append(";\r\n");
			}

			StringBuilder constructorWord = new StringBuilder(128 * propertySize);
			//无参构造
			if(noArgsConstructor){
				if(useLombok){
					annotationSet.add("@NoArgsConstructor");
					importSet.add("lombok.NoArgsConstructor");
				}else {
					constructorWord.append("\r\n\tpublic ").append(fileName).append("() {\r\n");
//					constructorWord.append("\t\tsuper();\r\n");
					constructorWord.append("\t}\r\n");
				}
			}
			//第一遍全参数构造器, 第二遍无主键构造器
			for (int i = 0; i < 2; i++) {
				if(!allArgsConstructor && i==0) continue; //是否添加全参构造
				if(useLombok && i==0){
					annotationSet.add("@AllArgsConstructor");
					importSet.add("lombok.AllArgsConstructor");
				}else {
					tmpWord.setLength(0);
					tmpWord.append("\r\n\tpublic ").append(fileName).append("(");
					for(Entry<String, String> entry:propertyMessage.entrySet()){
						if(i==1 && autoIncrementPropertyList.contains(entry.getKey())) continue;
						tmpWord.append(entry.getValue()).append(" ").append(entry.getKey()).append(", ");
					}
					constructorWord.append(tmpWord.substring(0, tmpWord.lastIndexOf(",")));
					constructorWord.append(") {\r\n");
//					constructorWord.append("\t\tsuper();\r\n");
					tmpWord = new StringBuilder();
					for(Entry<String, String> entry:propertyMessage.entrySet()){
						if(i==1 && autoIncrementPropertyList.contains(entry.getKey())) continue;
						tmpWord.append("\t\tthis.").append(entry.getKey()).append(" = ").append(entry.getKey()).append(";\r\n");
					}
					tmpWord.append("\t}\r\n");
					constructorWord.append(tmpWord);
				}
				if(!noPrimaryArgsConstructor || autoIncrementPropertyList.size()==0) break; //是否添加无主键构造
			}

			StringBuilder getterAndSetterWord = null;
			StringBuilder toStringWord = null;
			StringBuilder equalsAndHashCodeWord = null;
			if(useLombok){
				if(getterAndSetter && toString && equalsAndHashCode){
					annotationSet.add("@Data");
					importSet.add("lombok.Data");
				}else {
					if(getterAndSetter){
						annotationSet.add("@Getter");
						annotationSet.add("@Setter");
						importSet.add("lombok.Getter");
						importSet.add("lombok.Setter");
					}
					if(toString){
						annotationSet.add("@ToString");
						importSet.add("lombok.ToString");
					}
					if(equalsAndHashCode){
						annotationSet.add("@EqualsAndHashCode");
						importSet.add("lombok.EqualsAndHashCode");
					}
				}
			}else {
				//getter和setter方法
				if(getterAndSetter){
					getterAndSetterWord = new StringBuilder(160 * propertySize);
					getterAndSetterWord.append("\r\n");
					for(Entry<String, String> entry:propertyMessage.entrySet()){
						String propertyBigHump = entry.getKey().substring(0,1).toUpperCase()+entry.getKey().substring(1);
						getterAndSetterWord.append("\tpublic ").append(entry.getValue()).append(" get").append(propertyBigHump).append("() {\r\n")
								.append("\t\treturn ").append(entry.getKey()).append(";\r\n\t}\r\n")
								.append("\tpublic void set").append(propertyBigHump).append("(").append(entry.getValue())
								.append(" ").append(entry.getKey()).append(") {\r\n").append("\t\t")
								.append("this.").append(entry.getKey()).append(" = ").append(entry.getKey()).append(";\r\n\t}\r\n");
					}
				}
				//toString()
				if(toString){
					toStringWord = new StringBuilder(64 * propertySize);
					String projectPath = getProjectPath();
					boolean isEclipsePro = new File(projectPath + "/.project").exists();
					toStringWord.append("\r\n\t@Override\r\n");
					toStringWord.append("\tpublic String toString() {\r\n")
							.append("\t\treturn \"").append(fileName);
					if(isEclipsePro) toStringWord.append(" [");
					else toStringWord.append("{");
					toStringWord.append("\" +\r\n");
					tmpWord.setLength(0);
					for(Entry<String, String> entry:propertyMessage.entrySet()){
						if(!isEclipsePro && "String".equals(entry.getValue())){
							tmpWord.append("\t\t\t\t\", ").append(entry.getKey()).append("='\" + ").append(entry.getKey()).append(" + '\\''").append(" +\r\n");
						}else {
							tmpWord.append("\t\t\t\t\", ").append(entry.getKey()).append("=\" + ").append(entry.getKey()).append(" +\r\n");
						}
					}
					toStringWord.append("\t\t\t\t\"").append(tmpWord.substring(tmpWord.indexOf(",")+2)).append("\t\t\t\t'");
					if(isEclipsePro) toStringWord.append("]");
					else toStringWord.append("}");
					toStringWord.append("';\r\n\t}\r\n");
				}
				//equals和hashCode
				if(equalsAndHashCode){
					equalsAndHashCodeWord = new StringBuilder(128 * propertySize);
					if(useObjectsMethod){ //使用Objects方法
						importSet.add("java.util.Objects");
						//equals
						tmpWord.setLength(0);
						tmpWord.append("\r\n\t@Override\r\n");
						tmpWord.append("\tpublic boolean equals(Object o) {\r\n")
								.append("\t\tif (this == o) return true;\r\n")
								.append("\t\tif (o == null || getClass() != o.getClass()) return false;\r\n")
								.append("\t\t").append(fileName).append(" that = (").append(fileName).append(") o;\r\n")
								.append("\t\treturn ");
						for(Entry<String, String> entry:propertyMessage.entrySet()){
							tmpWord.append("Objects.equals(").append(entry.getKey()).append(", that.")
									.append(entry.getKey()).append(") && ");
						}
						equalsAndHashCodeWord.append(tmpWord.substring(0, tmpWord.lastIndexOf(" &")));
						equalsAndHashCodeWord.append(";\r\n\t}\r\n");
						//hashCode
						tmpWord.setLength(0);
						tmpWord.append("\r\n\t@Override\r\n");
						tmpWord.append("\tpublic int hashCode() {\r\n")
								.append("\t\treturn Objects.hash(");
						for(Entry<String, String> entry:propertyMessage.entrySet()){
							tmpWord.append(entry.getKey()).append(", ");
						}
						equalsAndHashCodeWord.append(tmpWord.substring(0, tmpWord.lastIndexOf(",")));
						equalsAndHashCodeWord.append(");\r\n\t}\r\n");
					}else { //使用默认方法
						//equals
						tmpWord.setLength(0);
						tmpWord.append("\r\n\t@Override\r\n");
						tmpWord.append("\tpublic boolean equals(Object o) {\r\n")
								.append("\t\tif (this == o) return true;\r\n")
								.append("\t\tif (o == null || getClass() != o.getClass()) return false;\r\n")
								.append("\t\t").append(fileName).append(" that = (").append(fileName).append(") o;\r\n");
						int i = 1;
						for(Entry<String, String> entry:propertyMessage.entrySet()){
							if(i != propertyMessage.size()){
								tmpWord.append("\t\tif (").append(entry.getKey()).append(" != null ? !")
										.append(entry.getKey()).append(".equals(that.").append(entry.getKey())
										.append(") : that.").append(entry.getKey()).append(" != null) return false;\r\n");
							}else {
								tmpWord.append("\t\treturn ").append(entry.getKey()).append(" != null ? ")
										.append(entry.getKey()).append(".equals(that.").append(entry.getKey())
										.append(") : that.").append(entry.getKey()).append(" == null;\r\n");
							}
							i++;
						}
						equalsAndHashCodeWord.append(tmpWord).append("\t}\r\n");
						//hashCode
						tmpWord.setLength(0);
						tmpWord.append("\r\n\t@Override\r\n");
						tmpWord.append("\tpublic int hashCode() {\r\n");
						i = 1;
						for(Entry<String, String> entry:propertyMessage.entrySet()){
							if(i == 1){
								tmpWord.append("\t\tint result = ").append(entry.getKey())
										.append(" != null ? ").append(entry.getKey()).append(".hashCode() : 0;\r\n");
							}else {
								tmpWord.append("\t\tresult = 31 * result + (").append(entry.getKey())
										.append(" != null ? ").append(entry.getKey()).append(".hashCode() : 0);\r\n");
							}
							i++;
						}
						tmpWord.append("\t\treturn result;\r\n");
						equalsAndHashCodeWord.append(tmpWord).append("\t}\r\n");
					}
				}
			}

			//自动导包
			StringBuilder importWord = null;
			if(autoImport && importSet.size()!=0){
				importWord = new StringBuilder("\r\n");
				for (String type : importSet) {
					importWord.append("import ").append(type).append(";\r\n");
				}
			}

			//类注解
			StringBuilder annotationWord = null;
			if(annotationSet.size()!=0){
				annotationWord = new StringBuilder();
				for (String annotation : annotationSet) {
					annotationWord.append("\r\n").append(annotation);
				}
			}

			//拼装
			if(packageName.length() != 0) word.append("package ").append(packageName).append(";\r\n");
			if(importWord != null) word.append(importWord);
			if(annotationWord != null) word.append(annotationWord);
			word.append("\r\npublic class ").append(fileName).append(" {\r\n");
			word.append(fieldWord);
			word.append(constructorWord);
			if(getterAndSetterWord != null) word.append(getterAndSetterWord);
			if(toStringWord != null) word.append(toStringWord);
			if(equalsAndHashCodeWord != null) word.append(equalsAndHashCodeWord);
			word.append("}");

			fos.write(word.toString().getBytes());
			fos.close();
		} catch (FileNotFoundException e) {
			System.err.println("文件没有找到!");
			e.printStackTrace();
			return;
		} catch (IOException e) {
			System.err.println("配置文件加载失败!");
			e.printStackTrace();
			return;
		}
	}

	/**
	 * 下划线转小驼峰
	 * @param str
	 * @return
	 */
	public static String lineToSmallHump(String str) {
		Matcher matcher = linePattern.matcher(str);
		StringBuffer sb = new StringBuffer();
		while (matcher.find()) {
			matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
		}
		matcher.appendTail(sb);
		if(sb.length() > 1){
			return sb.substring(0,1).toLowerCase() + sb.substring(1);
		}
		return sb.toString();
	}

	/**
	 * 下划线转大驼峰
	 * @param str
	 * @return
	 */
	public static String lineToBigHump(String str) {
		str = lineToSmallHump(str);
		if(str.length() == 0) return str;
		return str.substring(0,1).toUpperCase() + str.substring(1);
	}

	/**
	 * 获取当前项目/子项目路径, 前面带一个/
	 * @return
	 */
	private static String getProjectPath(){
		String classesPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
		try {
			classesPath = classesPath.replaceAll("\\+", "%2B");
			classesPath = URLDecoder.decode(classesPath,"UTF-8");
		} catch (Exception e) {
			System.err.println("路径URL解码错误,路径中可能含有特殊字符!");
			e.printStackTrace();
		}
		String projectPath = new File("").getAbsolutePath();
//        String subprojectName = classesPath.substring(projectPath.length() + 2, classesPath.indexOf("/", projectPath.length() + 2));
		String subprojectPath = classesPath.substring(0, classesPath.indexOf("/", projectPath.length() + 2));
		if(new File(subprojectPath + "/src").exists()){
			return subprojectPath;
		}
		return classesPath.substring(0, projectPath.length()+1);
	}

	/**
	 * 获取src路径, 前面带一个/
	 * @return
	 */
	private static String getSourcePath(){
		String projectPath = getProjectPath();
		String javaPath = projectPath + "/src/main/java/";
		if(new File(javaPath).exists()){
			return javaPath;
		}
		return projectPath + "/src/";
	}

	/**
	 * 获取包名
	 * @return
	 */
	private static String getPackageName(){
		String tmpPackageName = packageName.replaceAll("/", ".");
		tmpPackageName = tmpPackageName.replaceAll("\\\\", ".");
		if (tmpPackageName.startsWith(".")){
			tmpPackageName = tmpPackageName.substring(1);
		}
		if (tmpPackageName.endsWith(".")){
			tmpPackageName = tmpPackageName.substring(0, tmpPackageName.length()-1);
		}
		return tmpPackageName;
	}


}
